package com.llc.spring.cloud.sdk.core.proxy;

import com.llc.spring.cloud.sdk.common.annotation.*;
import com.llc.spring.cloud.sdk.core.ex.ProxyException;
import com.llc.spring.cloud.sdk.core.http.AbstractHttpClient;
import javassist.*;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * time 2018/8/24 19:12
 *
 * @author xiaojie.zhu <br>
 */
public class Proxy {

    public static <T> T newInstance(Class<T> t){
        String serverName = getServerName(t);
        ClassPool classPool = ClassPool.getDefault();
        ClassClassPath classClassPath = new ClassClassPath(Proxy.class);
        classPool.insertClassPath(classClassPath);

        CtClass cc = classPool.makeClass(genClassName(t));
        try {
            cc.setInterfaces(new CtClass[]{classPool.get(t.getName())});
        } catch (NotFoundException e) {
            throw new ProxyException("找不到父类 , " + t.getName() , e);
        }
        List<MethodDesc> methodDesces = getMethodDesces(t, serverName);
        for (MethodDesc methodDesce : methodDesces) {
            try {
                addMethod(cc, methodDesce);
            } catch (CannotCompileException e) {
                throw new ProxyException("生成代理对象失败" , e);
            }
        }

        try {
            Class<?> aClass = cc.toClass();
            return (T) aClass.newInstance();
        } catch (Exception e) {
            throw new ProxyException("创建proxy实例失败" , e);
        }


    }


    private static void addMethod(CtClass cc , MethodDesc methodDesc) throws CannotCompileException {
        String body = getMethodBody(methodDesc);
        CtMethod ctMethod = CtNewMethod.make(body, cc);
        cc.addMethod(ctMethod);
    }

    private static String getMethodBody(MethodDesc methodDesc) {
        StringBuffer sb = new StringBuffer();
        sb.append("public ");
        sb.append(methodDesc.getReturnType().getName());
        sb.append(" ");
        sb.append(methodDesc.getName());
        sb.append("(");

        List<ParamDesc> paramDescs = methodDesc.getParamDescs();
        StringBuffer sbData = new StringBuffer();
        if(paramDescs != null && paramDescs.size() > 0){
            for (int i = 0; i < paramDescs.size(); i++) {
                if(i != 0){
                    sb.append(" , ");
                }
                ParamDesc paramDesc = paramDescs.get(i);
                sb.append(paramDesc.getClazz().getName());
                sb.append(" ");
                sb.append(paramDesc.getName());
                if(paramDesc.isParam()){
                    sbData.append("parameter.put(\""+paramDesc.getName()+"\" , com.llc.spring.cloud.sdk.core.proxy.TypeUtil.getValue($"+(i + 1)+")); ");
                }
            }
        }
        sb.append("){ ");
        AbstractHttpClient.RequestType requestType = methodDesc.getRequestType();

        boolean singleObj = false;
        if(paramDescs != null && paramDescs.size() == 1 && !paramDescs.get(0).isParam() && AbstractHttpClient.RequestType.BODY == requestType){
            if(TypeUtil.isBasicType(paramDescs.get(0).getClazz())){
                throw new ProxyException("同时有@RequestBody注解，同时又只有一个参数，并且没有@Param注解，此参数必须为一个javabean");
            }
            singleObj = true;
        }

        sb.append("java.util.Map parameter = new java.util.HashMap(); ");
        sb.append(sbData);
        sb.append("com.llc.spring.cloud.sdk.core.http.HttpClient httpClient = new com.llc.spring.cloud.sdk.core.http.DefaultHttpClient(");

        sb.append("\""+handlerPath(methodDesc.getPath(),methodDesc)+"\"");
        sb.append(",");
        sb.append("parameter");
        sb.append(" , ");
        if(methodDesc.getPostBodyIndex() == -1){
            sb.append(" null ");
        }else{
            sb.append(" $" + (methodDesc.getPostBodyIndex() + 1) );
        }

        sb.append(" , ");
        sb.append(String.valueOf(methodDesc.isGet()));
        sb.append(");");
        sb.append("String data = httpClient.getResult();");
        if(!"void".equals(methodDesc.getReturnType().getName())){
            sb.append(methodDesc.getReturnType().getName());
            sb.append(" result = com.llc.spring.cloud.sdk.core.http.ResultUtil.parse(data , "+methodDesc.getReturnType().getName()+".class);");
            sb.append("return ("+TypeUtil.getStringReturnType(methodDesc.getReturnType())+") result;");
        }
        sb.append("}");
        return sb.toString();
    }

    static Pattern pattern = Pattern.compile("\\$\\{\\w+\\}");

    private static String handlerPath(String path , MethodDesc methodDesc){
        Matcher matcher = pattern.matcher(path);
        List<String> pat = new LinkedList<>();

        while (matcher.find()){
            String group = matcher.group();
            if(pat.contains(group)){
                throw new RuntimeException("不可以有相同名称的表达式");
            }
            pat.add(group);
        }

        for (int i = 0; i < pat.size(); i++) {
            String p = pat.get(i);
            int valueIndex = findValue(methodDesc, p.substring(2 , p.length() - 1));

            path = path.replace(p , "\"+$"+(valueIndex + 1)+"+\"");
        }

        return path;
    }

    private static int findValue(MethodDesc methodDesc , String key){
        List<ParamDesc> paramDescs = methodDesc.getParamDescs();
        for (int i = 0; i < paramDescs.size(); i++) {
            ParamDesc paramDesc = paramDescs.get(i);
            if(paramDesc.isParam() && key.equals(paramDesc.getName())){
                return i;
            }
        }

        throw new RuntimeException("找不到相应表达式的值 : " + key);
    }


    private static List<MethodDesc> getMethodDesces(Class<?> t, String serverName) {
        List<MethodDesc> methodDescs = new LinkedList<>();
        Method[] methods = t.getDeclaredMethods();
        for (Method method : methods) {
            try {
                MethodDesc methodDesc = getMethodDesc(method, serverName);
                methodDescs.add(methodDesc);
            } catch (Exception e) {
                throw new ProxyException("create proxy fail , " + t.getName() + "," + method.getName() , e);
            }
        }
        return methodDescs;
    }

    private static MethodDesc getMethodDesc(Method method, String serverName)throws Exception{
        MethodDesc methodDesc = new MethodDesc();
        methodDesc.setName(method.getName());
        methodDesc.setReturnType(method.getReturnType());
        boolean get;
        String url;
        Get gett = method.getAnnotation(Get.class);
        if(gett != null){
            get = true;
            url = gett.value();
        }else{
            Post post = method.getAnnotation(Post.class);
            if(post != null){
                get = false;
                url = post.value();
            }else{
                throw new ProxyException("@Get or @Post must be one");
            }
        }
        if(!url.startsWith("/")){
            url = "/" + url;
        }

        methodDesc.setGet(get);
        methodDesc.setPath(serverName + url);
        ParamEntry paramEntry = getParamDesc(method);
        methodDesc.setParamDescs(paramEntry.getParamDescs());
        methodDesc.setPostBodyIndex(paramEntry.getBodyBeanIndex());
        return methodDesc;
    }

    private static ParamEntry getParamDesc(Method method) {
        int postBodyIndex = -1;
        List<ParamDesc> paramDescs = new LinkedList<>();
        Parameter[] parameters = method.getParameters();
        if(parameters != null && parameters.length > 0){
            for (int i = 0 ; i < parameters.length ; i ++) {
                Parameter parameter = parameters[i];
                Param paramAnnotation = parameter.getAnnotation(Param.class);
                String key = "arg" + i;
                if(paramAnnotation != null){
                    key = paramAnnotation.value();
                }
                paramDescs.add(new ParamDesc(paramAnnotation != null , key , parameter.getType()));

                RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
                if(requestBody != null){
                    if(postBodyIndex != -1){
                        throw new RuntimeException("一个方法中不可以有多个带有@RequestBody的参数");
                    }
                    postBodyIndex = i;
                }
            }
        }
        return new ParamEntry(paramDescs, postBodyIndex);
    }

    private static String getServerName(Class<?> t){
        EnableSpringCloudApi springCloudApi = t.getAnnotation(EnableSpringCloudApi.class);
        if(springCloudApi == null){
            throw new ProxyException("not found @EnableSpringCloudApi in " + t.getName());
        }

        String value = springCloudApi.value();
        if(value.startsWith("/")){
            value = value.substring(1);
        }
        if(value.endsWith("/")){
            value = value.substring(0,value.length() - 1);
        }
        return value;
    }

    private static String genClassName(Class<?> c){
        return c.getName() + "Proxy";
    }

}
